#include <stdint.h>
#include <endian.h>
#include <stddef.h>

#define STYLE_9
#include "fn85.h"

static const char encoding[] = "0123456789"
	"abcdefghijklmnopqrstuvwxyz"
	"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
	"!$%&*+,-:;<=>?@[]^_`{|}";

static const uint8_t decoding[256] = {
	['0'] = 1,  ['1'] = 2,  ['2'] = 3,  ['3'] = 4,  ['4'] = 5,  ['5'] = 6,
	['6'] = 7,  ['7'] = 8,  ['8'] = 9,  ['9'] = 10, ['a'] = 11, ['b'] = 12,
	['c'] = 13, ['d'] = 14, ['e'] = 15, ['f'] = 16, ['g'] = 17, ['h'] = 18,
	['i'] = 19, ['j'] = 20, ['k'] = 21, ['l'] = 22, ['m'] = 23, ['n'] = 24,
	['o'] = 25, ['p'] = 26, ['q'] = 27, ['r'] = 28, ['s'] = 29, ['t'] = 30,
	['u'] = 31, ['v'] = 32, ['w'] = 33, ['x'] = 34, ['y'] = 35, ['z'] = 36,
	['A'] = 37, ['B'] = 38, ['C'] = 39, ['D'] = 40, ['E'] = 41, ['F'] = 42,
	['G'] = 43, ['H'] = 44, ['I'] = 45, ['J'] = 46, ['K'] = 47, ['L'] = 48,
	['M'] = 49, ['N'] = 50, ['O'] = 51, ['P'] = 52, ['Q'] = 53, ['R'] = 54,
	['S'] = 55, ['T'] = 56, ['U'] = 57, ['V'] = 58, ['W'] = 59, ['X'] = 60,
	['Y'] = 61, ['Z'] = 62, ['!'] = 63, ['$'] = 64, ['%'] = 65, ['&'] = 66,
	['*'] = 67, ['+'] = 68, [','] = 69, ['-'] = 70, [':'] = 71, [';'] = 72,
	['<'] = 73, ['='] = 74, ['>'] = 75, ['?'] = 76, ['@'] = 77, ['['] = 78,
	[']'] = 79, ['^'] = 80, ['_'] = 81, ['`'] = 82, ['{'] = 83, ['|'] = 84,
	['}'] = 85
};


void
fn85_encode(char *des, const void *src, size_t units)
{
	const uint32_t *bytes = src;

	for (; units; --units) {
		uint32_t val = 0;

		/* treat bytes as 256-radix number */
		val = be32toh(*bytes++);

		/* convert to five 85-radix chars of des ("big endian") */
		for (int div = 85 * 85 * 85 * 85; div; div /= 85, ++des)
			*des = encoding[val / div % 85];
	}
}

Fn85_err
fn85_decode(void *des, const char *src, size_t units,
	    size_t *pos, const char **error)
{
	uint32_t *bytes = des;

	for (; units; --units) {
		uint_fast64_t val = 0;

		for (int step = 0; step < 5; ++step, ++src, ++*pos) {
			uint8_t char_val = decoding[(uint8_t) *src];

			if (!char_val) {
				if (error)
					*error = "Not a valid digit";

				return FN85_INVALID;
			}

			if ((val = val * 85 + char_val - 1) > UINT32_MAX) {
				if (error)
					*error = "Number too large";

				return FN85_TOO_HIGH;
			}
		}

		*bytes++ = htobe32(val);
	}

	return FN85_OKAY;
}
